package com.laivi.basic.module.analysic;

import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import net.sourceforge.openforecast.DataPoint;
import net.sourceforge.openforecast.DataSet;
import net.sourceforge.openforecast.ForecastingModel;
import net.sourceforge.openforecast.Observation;
import net.sourceforge.openforecast.output.DelimitedTextOutputter;

import com.laivi.basic.module.log.Log;
import com.laivi.basic.module.log.Logs;
import com.laivi.basic.util.DataUtil;

/**
 * 数据预测封装处理类
 * @author laivi.zhu@gmail.com
 * @date 2014-5-31
 * description:
 */
public class Forecast {
	private static final Log log = Logs.getLog(Forecast.class);
	
	private Map<String,DataPoint> result;									//预测结果
	
	private double avgDeviation;											//平均误差率
	
	private int accuracy=-1;                                                //预测数据的精度控制
	
	private Map<Integer,String> keyMap=new HashMap<Integer,String>();		//预测下标与key的对应关系
	
	private Map<String,DataPoint> trainData;								//训练数据
	
	public Forecast() {
		trainData=new HashMap<String,DataPoint>();
	}
	
	public Forecast(Map<String,Double> trainMap){
		this();
		initData(trainMap);
	}

	/**
	 * 初始化一维的训练数据
	 * @date 2014-6-12
	 * @param trainMap:数据格式为key:value的键值对
	 * @return
	 */
	public DataSet initData(Map<String,Double> trainMap){
		DataSet datas=new DataSet();
		int i=1;
		for(Map.Entry<String, Double>entry:trainMap.entrySet()){
			DataPoint dp=new Observation(entry.getValue());
			keyMap.put(i, entry.getKey());
			dp.setIndependentValue("index", i++);
			datas.add(dp);
			trainData.put(entry.getKey(), dp);
			
		}
		return datas;
	}
	
	/**
	 * 初始化多维度的训练数据
	 * @date 2014-6-12
	 * @param trainDatas:多维度的键值对
	 * @param key 关键key键对应的字段
	 * @param value 值对应的字段
	 * @return
	 */
	public DataSet initData(List<Map<String,Object>> trainDatas,String key,String value){
		DataSet datas=new DataSet();
		int i=1;
		for(Map<String,Object> data:trainDatas){
			DataPoint dp=new Observation(Double.parseDouble(String.valueOf(data.get(value))));
			keyMap.put(i, String.valueOf(data.get(key)));
			dp.setIndependentValue("index", i++);
			for(Map.Entry<String, Object>entry:data.entrySet()){
				if(!value.equals(entry.getKey())&&!key.equals(entry.getKey())){
					dp.setIndependentValue(entry.getKey(), Double.parseDouble(String.valueOf(entry.getValue())));
				}
			}
			trainData.put(String.valueOf(data.get(key)), dp);
		}
		return datas;
	}
	
	/**
	 * 初始化包含训练数据的一维预测数据，训练数据是从start开始的
	 * @date 2014-6-12
	 * @param newKeys:新的预测数据
	 * @param start:训练数据的下标位置
	 * @return
	 */
	public DataSet initForecast(List<String> newKeys,int start){
		result=new LinkedHashMap<String,DataPoint>();
		DataSet datas=new DataSet();
		if(start<=keyMap.size()){
			for(Map.Entry<Integer, String>entry:keyMap.entrySet()){
				if(entry.getKey()>=start){
					DataPoint dp=new Observation(0.0);
					dp.setIndependentValue("index", entry.getKey());
					datas.add(dp);
				}
			}
		}
		int i=keyMap.size()+1;
		for(String key:newKeys){
			DataPoint dp=new Observation(0.0);
			if(keyMap.get(i)==null){
				keyMap.put(i,key);
			}
			dp.setIndependentValue("index", i++);
			datas.add(dp);
		}
		return datas;
	}
	
	/**
	 * 初始化新的一维预测数据
	 * @date 2014-6-12
	 * @param keys
	 * @return
	 */
	public DataSet initForecast(List<String> keys){
		result=new LinkedHashMap<String,DataPoint>();
		DataSet datas=new DataSet();
		int i=keyMap.size()+1;
		for(String key:keys){
			DataPoint dp=new Observation(0.0);
			if(keyMap.get(i)==null){
				keyMap.put(i,key);
			}
			dp.setIndependentValue("index", i++);
			datas.add(dp);
		}
		return datas;
	}
	
	/**
	 * 初始化新的多维预测数据
	 * @date 2014-6-12
	 * @param forecastData
	 * @param key
	 * @param start
	 * @return
	 */
	public DataSet initForecast(List<Map<String,Object>> forecastData,String key){
		result=new LinkedHashMap<String,DataPoint>();
		DataSet datas=new DataSet();
		int i=keyMap.size()+1;
		for(Map<String,Object> data:forecastData){
			DataPoint dp=new Observation(0.0);
			if(keyMap.get(i)==null){
				keyMap.put(i,String.valueOf(data.get(key)));
			}
			dp.setIndependentValue("index", i++);
			for(Map.Entry<String, Object>entry:data.entrySet()){
				if(!key.equals(entry.getKey())){
					dp.setIndependentValue(entry.getKey(), Double.parseDouble(String.valueOf(entry.getValue())));
				}
			}
			datas.add(dp);
		}
		return datas;
	}
	
	/**
	 * 预测数据
	 * @date 2014-6-12
	 * @param model
	 * @param trainData
	 * @param forecastData
	 * @return
	 */
	public ForecastingModel forecast(ForecastingModel model,DataSet trainData,DataSet forecastData){
		model.init(trainData);
		model.forecast(forecastData);
		for(DataPoint dp:forecastData){
			int index = (int)dp.getIndependentValue("index");
			if(this.accuracy!=-1){
				dp.setDependentValue(DataUtil.format(dp.getDependentValue(), this.accuracy));
			}
			result.put(keyMap.get(index), dp);
		}
		return model;
	}
	
	public double forecast(ForecastingModel model,int index){
		DataPoint dp=new Observation(0.0);
		dp.setIndependentValue("index", index);
		return model.forecast(dp);
	}
	
	public Map<String,Double> forecast(ForecastingModel model,int start,int end){
		DataSet dataSet=new DataSet();
		for(int i=start;i<=end;i++){
			DataPoint dp=new Observation(0.0);
			dp.setIndependentValue("index", i);
			dataSet.add(dp);
		}
		model.forecast(dataSet);
		return null;
	}
	
	/**
	 * 计算预测数据与训练数据的误差率
	 * @date 2014-6-12
	 */
	public void calDeviation(){
		double total=0;
		int i=0;
		for(Map.Entry<String,DataPoint>entry:result.entrySet()){
			if(trainData.get(entry.getKey())!=null){
				total+=Math.abs(entry.getValue().getDependentValue()-trainData.get(entry.getKey()).getDependentValue())/trainData.get(entry.getKey()).getDependentValue();
				i++;
			}
		}
		if(i==0){
			avgDeviation= 0;
		}else {
			avgDeviation=total/i;
		}
	}
	
	/**
	 * 计算预测数据与真实数据的误差率
	 * @date 2014-6-12
	 * @param obj
	 */
	public void calDeviation(Map<String,Object>obj){
		double total=0;
		int i=0;
		for(Map.Entry<String,DataPoint>entry:result.entrySet()){
			if(obj.get(entry.getKey())!=null){
				double trueVlaue=Double.parseDouble(String.valueOf(obj.get(entry.getKey())));
				total+=Math.abs(entry.getValue().getDependentValue()-trueVlaue)/trueVlaue;
				i++;
			}
		}
		if(i==0){
			avgDeviation= 0;
		}else {
			avgDeviation=total/i;
		}
	}
	
	/**
	 * 通过key获取对应的预测数据
	 * @date 2014-6-12
	 * @param key
	 * @return
	 */
	public Double getForecastValue(String key){
		if(result.get(key)!=null){
			return result.get(key).getDependentValue();
		}else{
			return null;
		}
	}
	
	/**
	 * 输出预测数据到控制台
	 * @date 2014-6-12
	 */
	public void outToConsole(){
		System.out.println("\t时间 \t         预测值");
		for(Map.Entry<String, DataPoint>entry:result.entrySet()){
			System.out.println("    "+entry.getKey()+" \t "+entry.getValue().getDependentValue());
		}
	}
	
	/**
	 * 输出预测数据到文件
	 * @date 2014-6-12
	 * @param filePath:文件路径
	 */
	public void outToFile(String filePath){
		try {
			DelimitedTextOutputter out=new DelimitedTextOutputter(filePath);
			DataSet datas=new DataSet();
			for(Map.Entry<String, DataPoint>entry:result.entrySet()){
				datas.add(entry.getValue());
			}
			out.setOutputHeaderRow(true);
			out.setDependentVariableName("预测值");
			out.output(datas);
			out.close();
		} catch (IOException e) {
			log.error("error:"+e.getMessage());
			//e.printStackTrace();
		}
	}

	public Map<String, DataPoint> getResult() {
		return result;
	}

	public double getAvgDeviation() {
		return avgDeviation;
	}
	
	public Map<String,DataPoint> getTrain(){
		return trainData;
	}
	
	public void setAccuracy(int accuracy) {
		this.accuracy = accuracy;
	}
}
